package com.wahoyu.exportwordtest.utils;

import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSONObject;
import com.wahoyu.exportwordtest.api.RemoteFileService;
import com.wahoyu.exportwordtest.domain.ChunkMultipartFile;
import com.wahoyu.exportwordtest.domain.ChunkMultipartForm;
import com.wahoyu.exportwordtest.domain.R;
import com.wahoyu.exportwordtest.domain.SysFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 * @ClassName FileChunkUploadUtils
 * @Description 文件分片上传工具
 * @Author Quyq
 * @Date 2023/3/14 14:10
 **/
public class FileChunkUploadUtils {

    private static final Logger log = LoggerFactory.getLogger(FileChunkUploadUtils.class);

    private final Map<String, Object> chunkData;

    private final RemoteFileService fileService;

    private final ChunkMultipartForm form;


    private final File file ;

    private List<Integer> existIndex = Collections.emptyList();

    private final ForkJoinPool pool = new ForkJoinPool(8);

    private final byte[] fileByte ;

    private final String contentType;

    private  Map<Integer , Integer> chunkSize;


    public FileChunkUploadUtils(ChunkMultipartForm form,Map<String, Object> chunkData ,
                                byte[] file ,String contentType,RemoteFileService fileService){
        Assert.notNull(form.getChunkSize());
        this.form = form;
        this.chunkData = chunkData;
        this.fileService = fileService;
        this.fileByte = file;
        this.file = null;
        this.contentType = contentType;
        init();
    }

    public FileChunkUploadUtils(ChunkMultipartForm form,Map<String, Object> chunkData ,
                                File file,RemoteFileService fileService){
        Assert.notNull(form.getChunkSize());
        this.form = form;
        this.chunkData = chunkData;
        this.fileService = fileService;
        this.file = file;
        this.fileByte = null;
        this.contentType = null;
        init();
    }

    private void init(){
        form.setUploadId((String) chunkData.get("uploadId"));

        List<Integer> cS = form.getChunkSize();
        if(Objects.isNull(cS) || cS.isEmpty())

            throw new RuntimeException("分片数量即大小不能为空");
        this.chunkSize = new HashMap<>();
        for (int i = 0 ; i < cS.size() ; i ++){
            this.chunkSize.put(i , cS.get(i));
        }
    }




    public String upload(){

        //获取已经上传过的分片
        String url = (String)chunkData.get("chunk_0");
        //上传参数
        String param = url.substring(url.indexOf("?") + 1);
        ChunkMultipartForm currForm = createFormByParam(param);

        R<List<Integer>> existId = fileService.chunkExist(currForm);
        if(R.SUCCESS != existId.getCode()){
            throw new RuntimeException("文件上传异常:" + existId.getMsg());
        }else if(!CollectionUtils.isEmpty(existId.getData()))
            this.existIndex =existId.getData();


        UploadTask task = new UploadTask(0);
        ForkJoinTask<Integer> taskResult = pool.submit(task);

        if(task.isCompletedAbnormally()){
            throw new RuntimeException("分片上传异常：" ,task.getException());
        }
        try {
            log.debug("开始上传。。。");
            Integer code = taskResult.get();
            log.debug("上传结束。。。");
            if(!code.equals(R.SUCCESS))
                return null;
        }catch (InterruptedException e){
            Thread.currentThread().interrupt();
        }catch (Exception e){
            throw new RuntimeException("上传任务执行异常：" , e);
        }

        R<String> result = fileService.completeMultipartUpload(form);
        if(R.SUCCESS == result.getCode())
            return result.getData();


        return null;
    }

    /**
     * 读取MultipartFile 字节流
     * @param file
     * @return
     */
    public static byte[] getMultipartFileByte(MultipartFile file){
        //读取流字节文件
        try(FastByteArrayOutputStream read = IoUtil.read(file.getInputStream(), true)) {
            return read.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage() , e);
        }
    }

    public static ChunkMultipartFile getMultipartFileHeadStream(byte[] bytes,String fileName , String contentType ){
        byte[] chunk = new byte[1024];
        System.arraycopy(bytes ,0 ,chunk,0 ,1024);
        return new ChunkMultipartFile(fileName, contentType , chunk);
    }

    public static ChunkMultipartFile getFileHeadStream(File file){
        try{
            File tmpF = new File(file.getCanonicalPath());

            return new ChunkMultipartFile(tmpF.getName(), MediaTypeFactory.getMediaType(tmpF.getName())
                    .orElse(MediaType.APPLICATION_OCTET_STREAM).toString(), getFileByte(tmpF));
        }catch (IOException e){
            throw new  RuntimeException("获取文件头部流异常" , e);
        }

    }

    private static byte[] getFileByte(File tmpF) throws IOException{
        byte[] chunk = new byte[1024];

        try(FileInputStream stream = new FileInputStream(tmpF)) {
            stream.read(chunk); //NOSONAR
        }
        return chunk;
    }


    protected ChunkMultipartForm createFormByParam(String param){
        String[] params = param.split("&");
        JSONObject val = new JSONObject();
        for(String p : params){
            String[] kv = p.split("=");
            val.put(kv[0],kv[1]);
        }
        return val.toJavaObject(ChunkMultipartForm.class);
    }

    public static ChunkMultipartFile localToMultipartFile(File file){
        byte[] curStream = new byte[(int) file.length()];

        try(FileInputStream stream = new FileInputStream(file)){
            stream.read(curStream); //NOSONAR
        }catch (IOException ex){
            throw new RuntimeException("转换文件类型异常：" , ex);
        }

        return new ChunkMultipartFile(file.getName(),MediaTypeFactory.getMediaType(file.getName())
                .orElse(MediaType.APPLICATION_OCTET_STREAM).toString(), curStream);
    }

    public static String getUniqueIdentifier(File file){
        try(InputStream fileStream = Files.newInputStream(file.toPath())) {
            return DigestUtils.md5DigestAsHex(fileStream);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage() , e);
        }
    }

    public static String getUniqueIdentifier(MultipartFile file){
        try(InputStream fileStream = file.getInputStream()) {
            return DigestUtils.md5DigestAsHex(fileStream);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage() , e);
        }
    }

    public static R<SysFile> uploadAndResult(FileChunkUploadUtils utils , String fileName, long fileSize){
        String url = utils.upload();
        if(org.springframework.util.StringUtils.hasText(url)){
            SysFile f = new SysFile();
            f.setName(fileName);
            f.setSize(fileSize);
            f.setUrl(url);
            return R.ok(f);
        }

        return R.fail("文件上传失败");
    }


    private class UploadTask extends RecursiveTask<Integer> {

        private static final long serialVersionUID = 8499654154438503503L;

        private final int index ;

        public UploadTask(int index){
            this.index = index;
        }

        @Override
        protected Integer compute() {


            String url = (String)chunkData.get("chunk_" + index);
            //上传参数
            String param = url.substring(url.indexOf("?") + 1);
            ChunkMultipartForm currForm = createFormByParam(param);

            UploadTask subTask = null;
            if(form.getChunkSize().size() > index+1){
                subTask = new UploadTask(index + 1);
                subTask.fork();
            }

            int resultCode = R.SUCCESS;
            //如果该分片没有上传，则开始上传
            if(!existIndex.contains(index)){

                //上传文件处理
                if(Objects.nonNull(fileByte)){
                    multipartFileGetFragment(currForm);
                }
                //本地文件处理
                else {
                    resultCode = localFileGetFragment(currForm);

                }

                if(resultCode == R.SUCCESS){

                    R<Boolean> r = fileService.chunkUpload(currForm);
                    if(Objects.isNull(r) || r.getCode() != R.SUCCESS)
                        resultCode = R.ERROR;
                    else
                        log.debug("分片 {} 上传完成" , index);
                }

            }


            if(Objects.nonNull(subTask)) {
                Integer join = subTask.join();
                if(!join.equals(R.SUCCESS))
                    return R.ERROR;
            }

            return resultCode;
        }

        //获取远程上传文件类型的指定分片片段
        private void multipartFileGetFragment(ChunkMultipartForm currForm){
            int offset = 0;
            for(int i = index - 1 ; i >= 0; i--){
                offset +=form.getChunkSize().get(i);
            }
            byte[] chunk = new byte[chunkSize.get(index)];


            System.arraycopy(fileByte ,offset ,chunk,0 ,chunkSize.get(index));

            currForm.setFile(new ChunkMultipartFile(form.getFileName(), contentType , chunk));
        }

        //获取本地上传文件类型的指定分片片段
        private int localFileGetFragment(ChunkMultipartForm currForm){
            int flag = R.SUCCESS;

            try{
                int i = 0;
                byte[] curStream = null;
                File tmpF = new File(file.getCanonicalPath());
                try(FileInputStream stream = new FileInputStream(tmpF)){
                    do{
                        byte[] otherStream = new byte[chunkSize.get(i)];
                        stream.read(otherStream); //NOSONAR
                        if(this.index == i)
                            curStream = otherStream;

                    }while (i++ < this.index);
                }

                currForm.setFile(new ChunkMultipartFile(tmpF.getName(), MediaTypeFactory.getMediaType(tmpF.getName())
                        .orElse(MediaType.APPLICATION_OCTET_STREAM).toString(), curStream));

            }catch (Exception ex){
                log.error("上传分片异常，分片批次："+ this.index , ex);
                flag = R.ERROR;
            }

            return flag;
        }

    }


}
